﻿namespace EasyToolkit.Excel.BindingTemplate.Renderer
{
    using System;
    using System.Collections.Generic;
    using EasyToolkit.BindingTemplate.Context;
    using EasyToolkit.BindingTemplate.Definition;
    using EasyToolkit.Excel.BindingTemplate.Definition;
    using Microsoft.Office.Interop.Excel;

    class ExcelPartRenderer_ : IDisposable
    {
        #region attributes and properties
        private Orientation orientation;
        private ExcelTemplateDefinition templateDefinition;
        private IBindingContext bindingContext;

        private Range firstRangeTo;
        private Range elementFirstRangeTo;

        private Range currentRenderingFrom;
        private Range currentRenderingTo;

        private int nextAxe1Offset;
        private int axe1Length;
        private int axe2Length;
        private int axe2Max;

        public List<List<IBindingContextItem>> Rows
        { get; private set; }

        public int Height
        { get; private set; }

        public int Width
        { get; private set; }

        public Range OutputRange
        { get; private set; }
        #endregion

        #region .ctors
        public ExcelPartRenderer_(ExcelTemplateView view)
        {
            templateDefinition = (ExcelTemplateDefinition)view.TemplateDefinition;
            orientation = view.Orientation;
            bindingContext = view.BindingContext;

            currentRenderingFrom = templateDefinition.TemplateFirstCell;
            firstRangeTo = elementFirstRangeTo = currentRenderingTo = view.FirstOutputCell;

            Init();
        }

        private ExcelPartRenderer_(LinkedTemplateDefinition linkedTemplateDefinition, IBindingContext bindingContext, ExcelPartRenderer host, int axe2Position, bool useOffset)
        {
            this.templateDefinition = (ExcelTemplateDefinition)linkedTemplateDefinition.TemplateDefinition;
            this.orientation = linkedTemplateDefinition.TemplateDefinition.Orientation;
            this.bindingContext = bindingContext;

            currentRenderingFrom = templateDefinition.TemplateFirstCell;
            if (orientation == Orientation.Horizontal)
            {
                if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Relative)
                    firstRangeTo = elementFirstRangeTo = currentRenderingTo = useOffset ? host.currentRenderingTo.Offset[1, 0] : host.currentRenderingTo;
                else
                {
                    currentRenderingTo = host.currentRenderingTo.Worksheet.Cells[host.GetNextLinkedTemplateAxe2Position(axe2Position), host.currentRenderingTo.Column];
                    firstRangeTo = elementFirstRangeTo = currentRenderingTo;
                }
            }
            else
            {
                if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Relative)
                    firstRangeTo = elementFirstRangeTo = currentRenderingTo = useOffset ? host.currentRenderingTo.Offset[0, 1] : host.currentRenderingTo;
                else
                {
                    currentRenderingTo = host.currentRenderingTo.Worksheet.Cells[host.currentRenderingTo.Row, host.GetNextLinkedTemplateAxe2Position(axe2Position)];
                    firstRangeTo = elementFirstRangeTo = currentRenderingTo;
                }
            }
            Init();
        }
        #endregion

        #region public methods
        public void Render()
        {
            if (bindingContext.ElementsToRender != null && bindingContext.ElementsToRender.Count > 0)
            {
                if (templateDefinition.HasLinkedTemplates)
                    ManageTemplateWithLinkedTemplates();
                else
                    ManageTemplateWithoutLinkedTemplates();

                if (templateDefinition.Orientation == Orientation.Horizontal)
                    ManageHorizontalTemplateWithLinkedTemplates();
                else
                    ManageVerticalTemplateWithLinkedTemplates();
                FinishToRenderPart();
            }
        }

        public void Dispose()
        {
            firstRangeTo = null;
            elementFirstRangeTo = null;

            currentRenderingFrom = null;
            currentRenderingTo = null;
        }
        #endregion

        #region private method
        private void Init()
        {
            Height = Width = 0;
            Rows = new List<List<IBindingContextItem>>();
            if (orientation == Orientation.Horizontal)
            {
                axe1Length = templateDefinition.Width;
                axe2Max = axe2Length = templateDefinition.Height;
            }
            else
            {
                axe1Length = templateDefinition.Height;
                axe2Max = axe2Length = templateDefinition.Width;
            }
        }

        private int GetNextLinkedTemplateAxe2Position(int axe2Position)
        {
            if (orientation == Orientation.Horizontal)
                return firstRangeTo.Row + axe2Position;
            else
                return firstRangeTo.Column + axe2Position;
        }

        private void ManageTemplateWithoutLinkedTemplates()
        {
            int cpt = 0;
            if (orientation == Orientation.Horizontal)
            {
                Width = templateDefinition.Width * bindingContext.ElementsToRender.Count;
                Height = templateDefinition.Height;

                for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                    Rows.Add(new List<IBindingContextItem>());

                for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
                {
                    cpt = 0;
                    for (int colId = 0; colId < templateDefinition.Width; colId++)
                    {
                        for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                            Rows[rowId].Add(templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);
                    }
                }

                for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
                {
                    cpt = 0;
                    for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                    {
                        List<IBindingContextItem> row = Rows[rowId];
                        foreach (IDefinitionPart part in templateDefinition.TemplatesPartsList[rowId])
                            row.Add(part == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);
                    }
                }
            }
            else
            {
                Width = templateDefinition.Width;
                Height = templateDefinition.Height * bindingContext.ElementsToRender.Count;

                for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
                {
                    cpt = 0;
                    for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                    {
                        List<IBindingContextItem> row = new List<IBindingContextItem>();
                        Rows.Add(row);
                        for (int colId = 0; colId < templateDefinition.Width; colId++)
                            row.Add(templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);
                    }
                }

                for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
                {
                    cpt = 0;
                    foreach (List<IDefinitionPart> parts in templateDefinition.TemplatesPartsList)
                    {
                        List<IBindingContextItem> row = new List<IBindingContextItem>();
                        Rows.Add(row);
                        foreach (IDefinitionPart part in parts)
                            row.Add(part == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);
                    }
                }
            }

            Range workingRange = currentRenderingTo.Resize[Height, Width];
            templateDefinition.TemplateCells.Copy(workingRange);
            currentRenderingTo = currentRenderingTo.Offset[Height, Width];
            workingRange = null;
        }

        private void ManageTemplateWithLinkedTemplates()
        {
            int nbrAxeToCopy;
            int cptBindingDefinition = 0;
            int cptLinkedDefinition = 0;
            IDefinitionPart definitionPart;
            List<IBindingContextItem> cellsToRender = new List<IBindingContextItem>();

            bool useOffsetAxe2;
            for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
            {
                nbrAxeToCopy = 0;
                cellsToRender.Clear();
                cptBindingDefinition = cptLinkedDefinition = 0;

                PrepareToRenderElement();
                for (int axe1 = 0; axe1 < axe1Length; axe1++)
                {
                    List<IBindingContextItem> row = new List<IBindingContextItem>();
                    Rows.Add(row);
                    for (int axe2 = 0; axe2 < axe2Length; axe2++)
                    {
                        useOffsetAxe2 = axe2 != 0;
                        if (orientation == Orientation.Horizontal)
                            definitionPart = templateDefinition.TemplatesParts[axe2, axe1];
                        else
                            definitionPart = templateDefinition.TemplatesParts[axe1, axe2];
                        if (definitionPart == null)
                        {
                            row.Add(null);//@@CurrentRenderingFrom.Value2);
                            cellsToRender.Add(null);
                        }
                        else
                        {
                            if (definitionPart.PartType == BindingPartType.BindingDefinition)
                            {
                                IBindingContextItem item = bindingContext.ElementsToRender[elementCpt].BindingContextItems[cptBindingDefinition++];
                                row.Add(item);
                                cellsToRender.Add(item);
                            }
                            else
                            {
                                IBindingContext linkedBindingContext = (IBindingContext)bindingContext.ElementsToRender[elementCpt].LinkedBindingContexts[cptLinkedDefinition++];
                                if (linkedBindingContext.ElementsToRender == null || linkedBindingContext.ElementsToRender.Count == 0)
                                {
                                    row.Add(null);
                                    cellsToRender.Add(null);
                                    TakeEmptyLinkedPartIntoAccount();
                                }
                                else
                                {
                                    LinkedTemplateDefinition linkedTemplateDefinition = (LinkedTemplateDefinition)definitionPart;
                                    int initCellsToRender = cellsToRender.Count;
                                    if (linkedTemplateDefinition.Positioning == LinkedTemplatePositioning.Absolute)
                                    {
                                        int wantedLinkedPosition = GetNextLinkedTemplateAxe2Position(axe2);
                                        int currentPosition = orientation == Orientation.Horizontal ? currentRenderingTo.Row : currentRenderingTo.Column;
                                        int swift = wantedLinkedPosition - currentPosition;
                                        if (swift >= 0 && cellsToRender.Count > swift)
                                        {
                                            cellsToRender.RemoveRange(swift, cellsToRender.Count - swift);
                                            int lastElement = wantedLinkedPosition - firstRangeTo.Column;
                                            row.RemoveRange(lastElement, row.Count - lastElement);
                                        }
                                    }
                                    RenderPartialTemplates(ref nbrAxeToCopy, cellsToRender, cellsToRender.Count, useOffsetAxe2, true);

                                    using (ExcelPartRenderer linkedContext = new ExcelPartRenderer(linkedTemplateDefinition, linkedBindingContext, this, axe2, useOffsetAxe2))
                                    {
                                        linkedContext.Render();
                                        IBindingContextItem[] nullArray = new IBindingContextItem[axe2];
                                        bool firstRow = true; ;
                                        foreach (List<IBindingContextItem> linkedRow in linkedContext.Rows)
                                        {
                                            if (firstRow)
                                            {
                                                firstRow = false;
                                                row.AddRange(linkedRow);
                                            }
                                            else
                                            {
                                                List<IBindingContextItem> newRow = new List<IBindingContextItem>(nullArray);
                                                Rows.Add(newRow);
                                                newRow.AddRange(linkedRow);
                                            }
                                        }
                                        TakeLinkedPartRenderingIntoAccount(linkedContext, axe1, axe2);
                                    }
                                }
                            }
                        }
                    }
                    if (cellsToRender.Count == axe2Length)
                    {
                        nbrAxeToCopy++;
                        cellsToRender.Clear();
                    }
                    else
                        FinishToRenderSecondAxe(cellsToRender, row, axe1 + 1);
                }
                FinishToRenderElement(nbrAxeToCopy);
            }
        }

        private void ManageVerticalTemplateWithLinkedTemplates()
        {
            for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
            {
                int cpt = 0;
                for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                {
                    List<IBindingContextItem> row = new List<IBindingContextItem>();
                    Rows.Add(row);

                    List<int> posLinks = templateDefinition.PositionLinkedTemplates[rowId];
                    if (posLinks == null)
                    {
                        Height++;
                        Range source = templateDefinition.TemplateFirstCell.Offset[rowId, 0];
                        for (int colId = 0; colId < templateDefinition.Width; colId++)
                            row.Add(templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);

                        source = source.Resize[1, templateDefinition.Width];
                        Range workingRange = currentRenderingTo.Resize[1, templateDefinition.Width];
                        source.Copy(workingRange);
                        currentRenderingTo = currentRenderingTo.Offset[1, 0];
                        source = null;

                        if (Width < templateDefinition.Width)
                            Width = templateDefinition.Width;
                    }
                    else
                    {

                    }
                }
            }
        }

        private void ManageVerticalTemplateWithLinkedTemplatesPart(int rowId)
        {
            Range source = templateDefinition.TemplateFirstCell.Offset[rowId, 0];
            for (int colId = 0; colId < templateDefinition.Width; colId++)
                row.Add(templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);

            source = source.Resize[1, templateDefinition.Width];
            Range workingRange = currentRenderingTo.Resize[1, templateDefinition.Width];
            source.Copy(workingRange);
            currentRenderingTo = currentRenderingTo.Offset[1, 0];
            source = null;
        }

        private void ManageHorizontalTemplateWithLinkedTemplates()
        {
            for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                Rows.Add(new List<IBindingContextItem>());

            for (int elementCpt = 0; elementCpt < bindingContext.ElementsToRender.Count; elementCpt++)
            {
                int cpt = 0;
                for (int colId = 0; colId < templateDefinition.Width; colId++)
                {
                    List<int> posLinks = templateDefinition.PositionLinkedTemplates[colId];
                    if (posLinks == null)
                    {
                        Width++;
                        for (int rowId = 0; rowId < templateDefinition.Height; rowId++)
                            Rows[rowId].Add(templateDefinition.TemplatesParts[rowId, colId] == null ? null : bindingContext.ElementsToRender[elementCpt].BindingContextItems[cpt++]);

                        Range source = templateDefinition.TemplateFirstCell.Offset[0, colId];
                        source = source.Resize[templateDefinition.Height, 1];
                        Range workingRange = currentRenderingTo.Resize[templateDefinition.Height, 1];
                        source.Copy(workingRange);
                        source = null;
                        currentRenderingTo = currentRenderingTo.Offset[0, 1];

                        if (Height < templateDefinition.Height)
                            Height = templateDefinition.Height;
                    }
                }
            }
        }

        private void PrepareToRenderElement()
        {
            if (orientation == Orientation.Horizontal)
                elementFirstRangeTo = elementFirstRangeTo.Offset[0, currentRenderingTo.Column - elementFirstRangeTo.Column + nextAxe1Offset];
            else
                elementFirstRangeTo = elementFirstRangeTo.Offset[currentRenderingTo.Row - elementFirstRangeTo.Row + nextAxe1Offset, 0];

            currentRenderingTo = elementFirstRangeTo;
            currentRenderingFrom = templateDefinition.TemplateFirstCell;
            nextAxe1Offset = 0;
        }

        private void FinishToRenderElement(int nbrAxeToCopy)
        {
            if (nbrAxeToCopy == 0)
                return;

            RenderPartialTemplates(ref nbrAxeToCopy, null, 0, false, false);
            Worksheet worksheetTo = currentRenderingTo.Worksheet;
            if (orientation == Orientation.Horizontal)
            {
                currentRenderingFrom = templateDefinition.TemplateFirstCell.Offset[0, axe1Length - nbrAxeToCopy];
                currentRenderingTo = worksheetTo.Cells[elementFirstRangeTo.Row, currentRenderingTo.Column + nextAxe1Offset];
            }
            else
            {
                currentRenderingFrom = templateDefinition.TemplateFirstCell.Offset[axe1Length - nbrAxeToCopy, 0];
                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row + nextAxe1Offset, elementFirstRangeTo.Column];
            }
            nextAxe1Offset = 0;
        }

        private void FinishToRenderPart()
        {
            if (templateDefinition.HasLinkedTemplates)
            {
                if (orientation == Orientation.Horizontal)
                {
                    Height = axe2Max;
                    Width = currentRenderingTo.Column - firstRangeTo.Column;
                }
                else
                {
                    Height = currentRenderingTo.Row - firstRangeTo.Row;
                    Width = axe2Max;
                }
            }
            if (Height > 0 && Width > 0)
                OutputRange = firstRangeTo.Resize[Height, Width];
        }

        private void FinishToRenderSecondAxe(List<IBindingContextItem> cellsToRender, List<IBindingContextItem> currentRow, int axeCpt)
        {
            if (cellsToRender.Count > 0)
            {
                int lastNotNull = cellsToRender.FindLastIndex(c => c != null);
                lastNotNull++;
                int toDelete = cellsToRender.Count - lastNotNull;
                cellsToRender.RemoveRange(lastNotNull, toDelete);
                currentRow.RemoveRange(currentRow.Count - toDelete, toDelete);

                int nbrAxeToCopy = 0;
                RenderPartialTemplates(ref nbrAxeToCopy, cellsToRender, cellsToRender.Count, false, true);
            }

            Worksheet worksheetFrom = currentRenderingFrom.Worksheet;
            Worksheet worksheetTo = currentRenderingTo.Worksheet;
            if (orientation == Orientation.Horizontal)
            {
                currentRenderingFrom = worksheetFrom.Cells[templateDefinition.TemplateFirstCell.Row, templateDefinition.TemplateFirstCell.Column + axeCpt];
                currentRenderingTo = worksheetTo.Cells[elementFirstRangeTo.Row, currentRenderingTo.Column + nextAxe1Offset];
            }
            else
            {
                currentRenderingFrom = worksheetFrom.Cells[templateDefinition.TemplateFirstCell.Row + axeCpt, templateDefinition.TemplateFirstCell.Column];
                currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row + nextAxe1Offset, elementFirstRangeTo.Column];
            }
            nextAxe1Offset = 0;
            cellsToRender.Clear();
        }

        private void TakeEmptyLinkedPartIntoAccount()
        {
            if (orientation == Orientation.Horizontal)
            {
                currentRenderingFrom = currentRenderingFrom.Offset[1, 0];
                currentRenderingTo = currentRenderingTo.Offset[1, 0];
            }
            else
            {
                currentRenderingFrom = currentRenderingFrom.Offset[0, 1];
                currentRenderingTo = currentRenderingTo.Offset[0, 1];
            }
        }

        private void TakeLinkedPartRenderingIntoAccount(ExcelPartRenderer partContext, int axe1, int axe2)
        {
            int sizeAxe2;
            Worksheet worksheetTo = currentRenderingTo.Worksheet;
            if (orientation == Orientation.Horizontal)
            {
                nextAxe1Offset = partContext.Width;
                currentRenderingFrom = templateDefinition.TemplateFirstCell.Offset[axe2 + 1, axe1];
                currentRenderingTo = worksheetTo.Cells[partContext.firstRangeTo.Row + partContext.Height, partContext.firstRangeTo.Column];
                sizeAxe2 = currentRenderingTo.Row - firstRangeTo.Row;
            }
            else
            {
                nextAxe1Offset = partContext.Height;
                currentRenderingFrom = templateDefinition.TemplateFirstCell.Offset[axe1, axe2 + 1];
                currentRenderingTo = worksheetTo.Cells[partContext.firstRangeTo.Row, partContext.firstRangeTo.Column + partContext.Width];
                sizeAxe2 = currentRenderingTo.Column - firstRangeTo.Column;
            }
            if (sizeAxe2 > axe2Max)
                axe2Max = sizeAxe2;
        }

        private void RenderPartialTemplates(ref int nbrAxeToCopy, List<IBindingContextItem> cellsToRender, int axe2SwiftFrom, bool useOffset, bool recalRenderingTo)
        {
            if (nbrAxeToCopy > 0)
            {
                Worksheet worksheetFrom = templateDefinition.TemplateFirstCell.Worksheet;
                Worksheet worksheetTo = currentRenderingTo.Worksheet;
                if (orientation == Orientation.Horizontal)
                {
                    currentRenderingFrom = worksheetFrom.Cells[templateDefinition.TemplateFirstCell.Row, currentRenderingFrom.Column];
                    currentRenderingTo = worksheetTo.Cells[elementFirstRangeTo.Row, currentRenderingTo.Column];
                }
                else
                {
                    currentRenderingFrom = worksheetFrom.Cells[currentRenderingFrom.Row, templateDefinition.TemplateFirstCell.Column];
                    currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row, elementFirstRangeTo.Column];
                }

                Range rangeTo = currentRenderingTo;
                Range rangeFrom = currentRenderingFrom;
                if (orientation == Orientation.Horizontal)
                {
                    rangeFrom = rangeFrom.Resize[templateDefinition.Height, nbrAxeToCopy];
                    currentRenderingFrom = worksheetFrom.Cells[templateDefinition.TemplateFirstCell.Row, currentRenderingFrom.Column + nbrAxeToCopy];
                    currentRenderingTo = worksheetTo.Cells[elementFirstRangeTo.Row, currentRenderingTo.Column + nbrAxeToCopy];
                }
                else
                {
                    rangeFrom = rangeFrom.Resize[nbrAxeToCopy, templateDefinition.Width];
                    currentRenderingFrom = worksheetFrom.Cells[currentRenderingFrom.Row + nbrAxeToCopy, templateDefinition.TemplateFirstCell.Column];
                    currentRenderingTo = worksheetTo.Cells[currentRenderingTo.Row + nbrAxeToCopy, elementFirstRangeTo.Column];
                }
                rangeFrom.Copy(rangeTo);
                nbrAxeToCopy = 0;
            }

            if (cellsToRender != null && cellsToRender.Count > 0)
            {
                int nbrBindingDefinitions = cellsToRender.Count;
                Range rangeTo = currentRenderingTo;
                Range rangeFrom = currentRenderingFrom;
                int lastNotNull = cellsToRender.Count; //@@cellsToRender.FindLastIndex(c => c != null) + 1; 
                if (orientation == Orientation.Horizontal)
                {
                    if (lastNotNull > 0)
                        rangeFrom = rangeFrom.Resize[lastNotNull, 1];
                    currentRenderingFrom = currentRenderingFrom.Offset[axe2SwiftFrom, 0];

                    if (recalRenderingTo)
                    {
                        currentRenderingTo = currentRenderingTo.Offset[useOffset ? nbrBindingDefinitions - 1 : nbrBindingDefinitions, 0];
                        int sizeAxe2 = currentRenderingTo.Row - firstRangeTo.Row;
                        if (sizeAxe2 > axe2Max)
                            axe2Max = sizeAxe2;
                    }
                }
                else
                {
                    if (lastNotNull > 0)
                        rangeFrom = rangeFrom.Resize[1, lastNotNull];
                    currentRenderingFrom = currentRenderingFrom.Offset[0, axe2SwiftFrom];

                    if (recalRenderingTo)
                    {
                        currentRenderingTo = currentRenderingTo.Offset[0, useOffset ? nbrBindingDefinitions - 1 : nbrBindingDefinitions];
                        int sizeAxe2 = currentRenderingTo.Column - firstRangeTo.Column;
                        if (sizeAxe2 > axe2Max)
                            axe2Max = sizeAxe2;
                    }
                }
                if (lastNotNull > 0)
                    rangeFrom.Copy(rangeTo);
                cellsToRender.Clear();
            }
        }
        #endregion
    }
}
